﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Collections;
using System.Text.RegularExpressions;

namespace Dycox.Utils
{
    /// <summary>
    /// Codes copied from System.Web.DataBinder class.
    /// Hope MS does not mind of this.
    /// </summary>
    public static class DataBinderUtil
    {
        //public static object Eval(object dataItem, string expression)
        //{

        //}

        //private static object GetPropertyValue(object obj, string propertyName)
        //{
        //    if (obj != null)
        //    {

        //        var coll = TypeDescriptor.GetProperties(obj);
        //        var prop = coll[propertyName];

        //        if (prop != null)
        //        {
        //            prop.GetValue();
        //        }
        //    }

        //    return null;
        //}

        private static readonly char[] expressionPartSeparator = new char[]
		{
			'.'
		};
        private static readonly char[] indexExprStartChars = new char[]
		{
			'[',
			'('
		};
        private static readonly char[] indexExprEndChars = new char[]
		{
			']',
			')'
		};

        public static readonly char[] Separators = expressionPartSeparator.Union(indexExprStartChars).Union(indexExprEndChars).ToArray();

        /// <summary>
        ///               Evaluates data-binding expressions at run time.
        ///           </summary>
        /// <returns>
        ///               An <see cref="T:System.Object" /> instance that results from the evaluation of the data-binding expression.
        ///           </returns>
        /// <param name="container">
        ///               The object reference against which the expression is evaluated. This must be a valid object identifier in the page's specified language. 
        ///           </param>
        /// <param name="expression">
        ///               The navigation path from the <paramref name="container" /> object to the public property value to be placed in the bound control property. This must be a string of property or field names separated by periods, such as Tables[0].DefaultView.[0].Price in C# or Tables(0).DefaultView.(0).Price in Visual Basic. 
        ///           </param>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="expression" /> is null or is an empty string after trimming.
        ///           </exception>
        public static object Eval(object container, string expression, out PropertyDescriptor property)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }
            expression = expression.Trim();
            if (expression.Length == 0)
            {
                throw new ArgumentNullException("expression");
            }
            if (container == null)
            {
                property = null;
                return null;
            }
            string[] expressionParts = expression.Split(DataBinderUtil.expressionPartSeparator);

            return DataBinderUtil.Eval(container, expressionParts, out property);
        }

        public static object Eval(object container, string expression)
        {
            PropertyDescriptor p;
            return Eval(container, expression, out p);
        }

        public static readonly string ContainerDelimiter = "::";

        private static object Eval(object container, string[] expressionParts, out PropertyDescriptor property)
        {
            object obj = container;
            int num = 0;
            property = null;

            while (num < expressionParts.Length && obj != null)
            {
                string text = expressionParts[num];

                if (num == 0 && container is IContainerProvider)
                {
                    int p = text.IndexOf(ContainerDelimiter);
                    string name = null;

                    if (p > 0)
                    {
                        name = text.Substring(0, p);
                        text = text.Substring(p + 2);
                    }

                    IContainerProvider prod = (IContainerProvider)container;
                    if (name != null) obj = prod.GetContainer(name);
                    else obj = prod.DefaultContainer;

                }

                if (text.IndexOfAny(DataBinderUtil.indexExprStartChars) < 0)
                {
                    obj = DataBinderUtil.GetPropertyValue(obj, text, out property);
                }
                else
                {
                    obj = DataBinderUtil.GetIndexedPropertyValue(obj, text, ref property);
                }
                num++;
            }
            return obj;
        }
        /// <summary>
        ///               Evaluates data-binding expressions at run time and formats the result as a string.
        ///           </summary>
        /// <returns>
        ///               A <see cref="T:System.String" /> object that results from evaluating the data-binding expression and converting it to a string type.
        ///           </returns>
        /// <param name="container">
        ///               The object reference against which the expression is evaluated. This must be a valid object identifier in the page's specified language. 
        ///           </param>
        /// <param name="expression">
        ///               The navigation path from the <paramref name="container" /> object to the public property value to be placed in the bound control property. This must be a string of property or field names separated by periods, such as Tables[0].DefaultView.[0].Price in C# or Tables(0).DefaultView.(0).Price in Visual Basic. 
        ///           </param>
        /// <param name="format">
        ///               A .NET Framework format string (like those used by <see cref="M:System.String.Format(System.String,System.Object)" />) that converts the <see cref="T:System.Object" /> instance returned by the data-binding expression to a <see cref="T:System.String" /> object. 
        ///           </param>
        public static string Eval(object container, string expression, string format, string nullText, out PropertyDescriptor property)
        {
            object obj = DataBinderUtil.Eval(container, expression, out property);
            if (obj == null || obj == DBNull.Value)
            {
                return nullText ?? string.Empty;
            }
            if (string.IsNullOrEmpty(format))
            {
                return Formatting.ToString(obj, property);
            }
            return string.Format(format, obj);
        }

        public static string Eval(object container, string expression, string format)
        {
            return Eval(container, expression, format, null);
        }

        public static string Eval(object container, string expression, string format, string nullText)
        {
            PropertyDescriptor p;

            return Eval(container, expression, format, nullText, out p);
        }

        /// <summary>
        ///               Retrieves the value of the specified property of the specified object.
        ///           </summary>
        /// <returns>
        ///               The value of the specified property.
        ///           </returns>
        /// <param name="container">
        ///               The object that contains the property. 
        ///           </param>
        /// <param name="propName">
        ///               The name of the property that contains the value to retrieve. 
        ///           </param>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="container" /> is null.
        ///
        ///               -or- 
        ///           <paramref name="propName" /> is null or an empty string (""). 
        ///           </exception>
        /// <exception cref="T:System.Web.HttpException">
        ///               The object in <paramref name="container" /> does not have the property specified by <paramref name="propName" />. 
        ///           </exception>
        public static object GetPropertyValue(object container, string propName, out PropertyDescriptor propertyDescriptor)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            if (string.IsNullOrEmpty(propName))
            {
                throw new ArgumentNullException("propName");
            }
            
            propertyDescriptor = TypeDescriptor.GetProperties(container).Find(propName, true);
            if (propertyDescriptor != null)
            {
                return propertyDescriptor.GetValue(container);
            }

            throw new ArgumentException("Property " + propName + " not found.");
        }

        /// <summary>
        ///               Retrieves the value of the specified property of the specified object, and then formats the results.
        ///           </summary>
        /// <returns>
        ///               The value of the specified property in the format specified by <paramref name="format" />.
        ///           </returns>
        /// <param name="container">
        ///               The object that contains the property. 
        ///           </param>
        /// <param name="propName">
        ///               The name of the property that contains the value to retrieve. 
        ///           </param>
        /// <param name="format">
        ///               A string that specifies the format in which to display the results. 
        ///           </param>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="container" /> is null.
        ///
        ///               - or - 
        ///           <paramref name="propName" /> is null or an empty string (""). 
        ///           </exception>
        /// <exception cref="T:System.Web.HttpException">
        ///               The object in <paramref name="container" /> does not have the property specified by <paramref name="propName" />. 
        ///           </exception>
        public static string GetPropertyValue(object container, string propName, string format)
        {
            PropertyDescriptor p;
            object propertyValue = DataBinderUtil.GetPropertyValue(container, propName, out p);
            if (propertyValue == null || propertyValue == DBNull.Value)
            {
                return string.Empty;
            }
            if (string.IsNullOrEmpty(format))
            {
                return propertyValue.ToString();
            }
            return string.Format(format, propertyValue);
        }
        /// <summary>
        ///               Retrieves the value of a property of the specified container and navigation path.
        ///           </summary>
        /// <returns>
        ///               An object that results from the evaluation of the data-binding expression.
        ///           </returns>
        /// <param name="container">
        ///               The object reference against which <paramref name="expr" /> is evaluated. This must be a valid object identifier in the specified language for the page.
        ///           </param>
        /// <param name="expr">
        ///               The navigation path from the <paramref name="container" /> object to the public property value to place in the bound control property. This must be a string of property or field names separated by periods, such as Tables[0].DefaultView.[0].Price in C# or Tables(0).DefaultView.(0).Price in Visual Basic.
        ///           </param>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="container" /> is null.
        ///
        ///               - or -
        ///           <paramref name="expr" /> is null or an empty string ("").
        ///           </exception>
        /// <exception cref="T:System.ArgumentException">
        ///   <paramref name="expr" /> is not a valid indexed expression.
        ///
        ///               - or -
        ///           <paramref name="expr" /> does not allow indexed access.
        ///           </exception>
        private static object GetIndexedPropertyValue(object container, string expr, ref PropertyDescriptor propertyDescriptor)
        {
            if (container == null)
            {
                throw new ArgumentNullException("container");
            }
            if (string.IsNullOrEmpty(expr))
            {
                throw new ArgumentNullException("expr");
            }
            object result = null;
            bool flag = false;
            int num = expr.IndexOfAny(DataBinderUtil.indexExprStartChars);
            int num2 = expr.IndexOfAny(DataBinderUtil.indexExprEndChars, num + 1);
            if (num < 0 || num2 < 0 || num2 == num + 1)
            {
                throw new ArgumentException(expr + " is not a valid indexer expression.");
            }
            string text = null;
            object obj = null;
            string text2 = expr.Substring(num + 1, num2 - num - 1).Trim();
            if (num != 0)
            {
                text = expr.Substring(0, num);
            }
            if (text2.Length != 0)
            {
                if ((text2[0] == '"' && text2[text2.Length - 1] == '"') || (text2[0] == '\'' && text2[text2.Length - 1] == '\''))
                {
                    obj = text2.Substring(1, text2.Length - 2);
                }
                else
                {
                    if (char.IsDigit(text2[0]))
                    {
                        int num3;
                        flag = int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out num3);
                        if (flag)
                        {
                            obj = num3;
                        }
                        else
                        {
                            obj = text2;
                        }
                    }
                    else
                    {
                        obj = text2;
                    }
                }
            }
            if (obj == null)
            {
                throw new ArgumentException(expr + " is not a valid indexer expression.");
            }
            object obj2;
            if (text != null && text.Length != 0)
            {
                obj2 = DataBinderUtil.GetPropertyValue(container, text, out propertyDescriptor);
            }
            else
            {
                obj2 = container;
            }
            if (obj2 != null)
            {
                Array array = obj2 as Array;
                if (array != null && flag)
                {
                    result = array.GetValue((int)obj);
                }
                else
                {
                    if (obj2 is IList && flag)
                    {
                        result = ((IList)obj2)[(int)obj];
                    }
                    else
                    {
                        PropertyInfo property = obj2.GetType().GetProperty("Item", BindingFlags.Instance | BindingFlags.Public, null, null, new Type[]
						{
							obj.GetType()
						}, null);
                        if (property == null)
                        {
                            throw new ArgumentException("Indexer accessor not found on " + obj2.GetType().FullName);
                        }
                        result = property.GetValue(obj2, new object[]
						{
							obj
						});
                    }
                }
            }
            return result;
        }
        /*
        /// <summary>
        ///               Retrieves the value of the specified property for the specified container, and then formats the results.
        ///           </summary>
        /// <returns>
        ///               The value of the specified property in the format specified by <paramref name="format" />.
        ///           </returns>
        /// <param name="container">
        ///               The object reference against which the expression is evaluated. This must be a valid object identifier in the specified language for the page.
        ///           </param>
        /// <param name="propName">
        ///               The name of the property that contains the value to retrieve.
        ///           </param>
        /// <param name="format">
        ///               A string that specifies the format in which to display the results.
        ///           </param>
        private static string GetIndexedPropertyValue(object container, string propName, string format)
        {
            PropertyDescriptor property = null;
            object indexedPropertyValue = DataBinderUtil.GetIndexedPropertyValue(container, propName, ref property);
            if (indexedPropertyValue == null || indexedPropertyValue == DBNull.Value)
            {
                return string.Empty;
            }
            if (string.IsNullOrEmpty(format))
            {
                return indexedPropertyValue.ToString();
            }
            return string.Format(format, indexedPropertyValue);
        }*/

        internal static bool IsNull(object value)
        {
            return value == null || Convert.IsDBNull(value);
        }

        public static string EvalTemplate(string template, object data)
        {
            int index;
            
            if (data != null && (index = template.IndexOf('{')) >= 0 && template.IndexOf('}', index + 1) > index)
            {
                MatchEvaluator eval = (m) =>
                {
                    string expr = m.Groups[1].Value, format;
                    int p;

                    p = expr.LastIndexOf(':');
                    if (p > 0 && expr[p - 1] != ':')            // 避免两个引号导致冲突
                    {
                        format = "{0:" + expr.Substring(p + 1) + "}";
                        expr = expr.Substring(0, p);
                    }
                    else
                        format = null;

                    try
                    {
                        return Eval(data, expr, format);
                    }
                    catch
                    {
                        return m.Value;
                    }
                };

                template = Regex.Replace(template, @"\{([^\}]+)\}", eval);
            }

            return template;
        }

    }
}
